//
//  GJSUtilHTTPRequestClient.m
//  YuanXinTrack
//
//  Created by GJS on 2018/5/15.
//  Copyright © 2018年 GJS. All rights reserved.
//

#import "GJSUtilHTTPRequestClient.h"
#import "GJSHTTPSessionManager.h"

#define kNetworkRefreshToken @"refreshToken"

typedef void (^TryAgainBlock)(void);

NS_ASSUME_NONNULL_BEGIN

/**
 `AFOAuthCredential` models the credentials returned from an OAuth server, storing the token type, access & refresh tokens, and whether the token is expired.
 OAuth credentials can be stored in the user's keychain, and retrieved on subsequent launches.
 */
@interface GJSOAuthCredential : NSObject <NSCoding>

///--------------------------------------
/// @name Accessing Credential Properties
///--------------------------------------

/**
 The OAuth access token.
 */
@property (readonly, nonatomic, copy) NSString *accessToken;

/**
 The OAuth token type (e.g. "bearer").
 */
@property (readonly, nonatomic, copy) NSString *tokenType;

/**
 The OAuth refresh token.
 */
@property (readonly, nonatomic, copy) NSString *refreshToken;

/**
 The OAuth expiration.
 */
@property (readwrite, nonatomic, copy) NSDate *expiration;

/**
 Whether the OAuth credentials are expired.
 */
@property (readonly, nonatomic, assign, getter = isExpired) BOOL expired;

///--------------------------------------------
/// @name Creating and Initializing Credentials
///--------------------------------------------

/**
 Create an OAuth credential from a token string, with a specified type.
 @param token The OAuth token string.
 @param type The OAuth token type.
 */
+ (instancetype)credentialWithOAuthToken:(NSString *)token
                               tokenType:(NSString *)type;

/**
 Initialize an OAuth credential from a token string, with a specified type.
 @param token The OAuth token string.
 @param type The OAuth token type.
 */
- (id)initWithOAuthToken:(NSString *)token
               tokenType:(NSString *)type;

///----------------------------
/// @name Setting Refresh Token
///----------------------------

/**
 Set the credential refresh token, without a specific expiration
 @param refreshToken The OAuth refresh token.
 */
- (void)setRefreshToken:(NSString *)refreshToken;


/**
 Set the expiration on the access token. If no expiration is given by the OAuth2 provider,
 you may pass in [NSDate distantFuture]
 @param expiration The expiration of the access token. This must not be `nil`.
 */
- (void)setExpiration:(NSDate *)expiration;

/**
 Set the credential refresh token, with a specified expiration.
 @param refreshToken The OAuth refresh token.
 @param expiration The expiration of the access token. This must not be `nil`.
 */
- (void)setRefreshToken:(NSString *)refreshToken
             expiration:(NSDate *)expiration;

///-----------------------------------------
/// @name Storing and Retrieving Credentials
///-----------------------------------------

/**
 Stores the specified OAuth credential for a given web service identifier in the Keychain.
 with the default Keychain Accessibilty of kSecAttrAccessibleWhenUnlocked.
 @param credential The OAuth credential to be stored.
 @param identifier The service identifier associated with the specified credential.
 @return Whether or not the credential was stored in the keychain.
 */
+ (BOOL)storeCredential:(GJSOAuthCredential *)credential
         withIdentifier:(NSString *)identifier;

/**
 Stores the specified OAuth token for a given web service identifier in the Keychain.
 @param credential The OAuth credential to be stored.
 @param identifier The service identifier associated with the specified token.
 @param securityAccessibility The Keychain security accessibility to store the credential with.
 @return Whether or not the credential was stored in the keychain.
 */
+ (BOOL)storeCredential:(GJSOAuthCredential *)credential
         withIdentifier:(NSString *)identifier
      withAccessibility:(id)securityAccessibility;

/**
 Retrieves the OAuth credential stored with the specified service identifier from the Keychain.
 @param identifier The service identifier associated with the specified credential.
 @return The retrieved OAuth credential.
 */
+ (nullable GJSOAuthCredential *)retrieveCredentialWithIdentifier:(NSString *)identifier;

/**
 Deletes the OAuth credential stored with the specified service identifier from the Keychain.
 @param identifier The service identifier associated with the specified credential.
 @return Whether or not the credential was deleted from the keychain.
 */
+ (BOOL)deleteCredentialWithIdentifier:(NSString *)identifier;

@end

NS_ASSUME_NONNULL_END

NSString * const kGJSOAuth2CredentialServiceName = @"AFOAuthCredentialService";

static NSDictionary * AFKeychainQueryDictionaryWithIdentifier(NSString *identifier) {
    NSCParameterAssert(identifier);
    
    return @{
             (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
             (__bridge id)kSecAttrService: kGJSOAuth2CredentialServiceName,
             (__bridge id)kSecAttrAccount: identifier
             };
}

@interface GJSOAuthCredential()
@property (readwrite, nonatomic, copy) NSString *accessToken;
@property (readwrite, nonatomic, copy) NSString *tokenType;
@property (readwrite, nonatomic, copy) NSString *refreshToken;
//@property (readwrite, nonatomic, copy) NSDate *expiration;
@end


@implementation GJSOAuthCredential
//@dynamic expired;

#pragma mark -

+ (instancetype)credentialWithOAuthToken:(NSString *)token
                               tokenType:(NSString *)type
{
    return [[self alloc] initWithOAuthToken:token tokenType:type];
}

- (id)initWithOAuthToken:(NSString *)token
               tokenType:(NSString *)type
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    self.accessToken = token;
    self.tokenType = type;
    
    return self;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@ accessToken:\"%@\" tokenType:\"%@\" refreshToken:\"%@\" expiration:\"%@\">", [self class], self.accessToken, self.tokenType, self.refreshToken, self.expiration];
}

- (void)setRefreshToken:(NSString *)refreshToken
{
    _refreshToken = refreshToken;
}

- (void)setExpiration:(NSDate *)expiration
{
    _expiration = expiration;
}

- (void)setRefreshToken:(NSString *)refreshToken
             expiration:(NSDate *)expiration
{
    NSParameterAssert(refreshToken);
    NSParameterAssert(expiration);
    
    self.refreshToken = refreshToken;
    self.expiration = expiration;
}

- (BOOL)isExpired {
    return [self.expiration compare:[NSDate date]] == NSOrderedAscending;
}

#pragma mark Keychain

+ (BOOL)storeCredential:(GJSOAuthCredential *)credential
         withIdentifier:(NSString *)identifier
{
    id securityAccessibility = nil;
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 43000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-compare"
    if (&kSecAttrAccessibleWhenUnlocked != NULL) {
        securityAccessibility = (__bridge id)kSecAttrAccessibleWhenUnlocked;
    }
#pragma clang diagnostic pop
#endif
    
    return [[self class] storeCredential:credential withIdentifier:identifier withAccessibility:securityAccessibility];
}

+ (BOOL)storeCredential:(GJSOAuthCredential *)credential
         withIdentifier:(NSString *)identifier
      withAccessibility:(id)securityAccessibility
{
    NSMutableDictionary *queryDictionary = [AFKeychainQueryDictionaryWithIdentifier(identifier) mutableCopy];
    
    NSMutableDictionary *updateDictionary = [NSMutableDictionary dictionary];
    updateDictionary[(__bridge id)kSecValueData] = [NSKeyedArchiver archivedDataWithRootObject:credential];
    
    if (securityAccessibility) {
        updateDictionary[(__bridge id)kSecAttrAccessible] = securityAccessibility;
    }
    
    OSStatus status;
    BOOL exists = ([self retrieveCredentialWithIdentifier:identifier] != nil);
    
    if (exists) {
        status = SecItemUpdate((__bridge CFDictionaryRef)queryDictionary, (__bridge CFDictionaryRef)updateDictionary);
    } else {
        [queryDictionary addEntriesFromDictionary:updateDictionary];
        status = SecItemAdd((__bridge CFDictionaryRef)queryDictionary, NULL);
    }
    
    return (status == errSecSuccess);
}

+ (BOOL)deleteCredentialWithIdentifier:(NSString *)identifier {
    NSMutableDictionary *queryDictionary = [AFKeychainQueryDictionaryWithIdentifier(identifier) mutableCopy];
    
    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)queryDictionary);
    
    return (status == errSecSuccess);
}

+ (GJSOAuthCredential *)retrieveCredentialWithIdentifier:(NSString *)identifier {
    NSMutableDictionary *queryDictionary = [AFKeychainQueryDictionaryWithIdentifier(identifier) mutableCopy];
    queryDictionary[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
    queryDictionary[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
    
    CFDataRef result = nil;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)queryDictionary, (CFTypeRef *)&result);
    
    if (status != errSecSuccess) {
        return nil;
    }
    
    return [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)result];
}

#pragma mark - NSCoding

- (id)initWithCoder:(NSCoder *)decoder {
    self = [super init];
    self.accessToken = [decoder decodeObjectForKey:NSStringFromSelector(@selector(accessToken))];
    self.tokenType = [decoder decodeObjectForKey:NSStringFromSelector(@selector(tokenType))];
    self.refreshToken = [decoder decodeObjectForKey:NSStringFromSelector(@selector(refreshToken))];
    self.expiration = [decoder decodeObjectForKey:NSStringFromSelector(@selector(expiration))];
    
    return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:self.accessToken forKey:NSStringFromSelector(@selector(accessToken))];
    [encoder encodeObject:self.tokenType forKey:NSStringFromSelector(@selector(tokenType))];
    [encoder encodeObject:self.refreshToken forKey:NSStringFromSelector(@selector(refreshToken))];
    [encoder encodeObject:self.expiration forKey:NSStringFromSelector(@selector(expiration))];
}

@end

#pragma mark -

@interface GJSOAuthManager : GJSHTTPSessionManager

///------------------------------------------
/// @name Accessing OAuth 2 Client Properties
///------------------------------------------

/**
 The service provider identifier used to store and retrieve OAuth credentials by `AFOAuthCredential`. Equivalent to the hostname of the client `baseURL`.
 */
@property (readonly, nonatomic, copy) NSString *serviceProviderIdentifier;

/**
 The client identifier issued by the authorization server, uniquely representing the registration information provided by the client.
 */
@property (readonly, nonatomic, copy) NSString *clientID;

/**
 Whether to encode client credentials in a Base64-encoded HTTP `Authorization` header, as opposed to the request body. Defaults to `YES`.
 */
@property (nonatomic, assign) BOOL useHTTPBasicAuthentication;

///------------------------------------------------
/// @name Creating and Initializing OAuth 2 Managers
///------------------------------------------------

/**
 Creates and initializes an `AFOAuth2Manager` object with the specified base URL, client identifier, and secret.
 @param url The base URL for the HTTP client. This argument must not be `nil`.
 @param clientID The client identifier issued by the authorization server, uniquely representing the registration information provided by the client. This argument must not be `nil`.
 @param secret The client secret.
 @return The newly-initialized OAuth 2 manager
 */
+ (instancetype)managerWithBaseURL:(NSURL *)url
                          clientID:(NSString *)clientID
                            secret:(NSString *)secret;

+ (instancetype)managerWithBaseURL:(NSURL *)url
              sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration
                          clientID:(NSString *)clientID
                            secret:(NSString *)secret;

/**
 Initializes an `AFOAuth2Manager` object with the specified base URL, client identifier, and secret. The communication to to the server will use HTTP basic auth by default (use `-(id)initWithBaseURL:clientID:secret:withBasicAuth:` to change this).
 @param url The base URL for the HTTP manager. This argument must not be `nil`.
 @param clientID The client identifier issued by the authorization server, uniquely representing the registration information provided by the client. This argument must not be `nil`.
 @param secret The client secret.
 @return The newly-initialized OAuth 2 client
 */
- (id)initWithBaseURL:(NSURL *)url
             clientID:(NSString *)clientID
               secret:(NSString *)secret;

- (id)initWithBaseURL:(NSURL *)url
 sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration
             clientID:(NSString *)clientID
               secret:(NSString *)secret;

///---------------------
/// @name Authenticating
///---------------------

/**
 Creates and enqueues an `NSURLSessionTask` to authenticate against the server using a specified username and password, with a designated scope.
 @param URLString The URL string used to create the request URL.
 @param username The username used for authentication
 @param password The password used for authentication
 @param scope The authorization scope
 @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the OAuth credential returned by the server.
 @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error returned from the server.
 */
- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                                 username:(NSString *)username
                                                 password:(NSString *)password
                                                    scope:(nullable NSString *)scope
                                                  success:(void (^)(GJSOAuthCredential *credential))success
                                                  failure:(void (^)(NSError *error))failure;

/**
 Creates and enqueues an `NSURLSessionTask` to authenticate against the server with a designated scope.
 @param URLString The URL string used to create the request URL.
 @param scope The authorization scope
 @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the OAuth credential returned by the server.
 @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error returned from the server.
 */
- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                                    scope:(nullable NSString *)scope
                                                  success:(void (^)(GJSOAuthCredential *credential))success
                                                  failure:(void (^)(NSError *error))failure;

/**
 Creates and enqueues an `NSURLSessionTask` to authenticate against the server using the specified refresh token.
 @param URLString The URL string used to create the request URL.
 @param refreshToken The OAuth refresh token
 @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the OAuth credential returned by the server.
 @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error returned from the server.
 */
- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                             refreshToken:(NSString *)refreshToken
                                                  success:(void (^)(GJSOAuthCredential *credential))success
                                                  failure:(void (^)(NSError *error))failure;

/**
 Creates and enqueues an `NSURLSessionTask` to authenticate against the server with an authorization code, redirecting to a specified URI upon successful authentication.
 @param URLString The URL string used to create the request URL.
 @param code The authorization code
 @param uri The URI to redirect to after successful authentication
 @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the OAuth credential returned by the server.
 @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error returned from the server.
 */
- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                                     code:(NSString *)code
                                              redirectURI:(NSString *)uri
                                                  success:(void (^)(GJSOAuthCredential *credential))success
                                                  failure:(void (^)(NSError *error))failure;

/**
 Creates and enqueues an `NSURLSessionTask` to authenticate against the server with the specified parameters.
 @param URLString The URL string used to create the request URL.
 @param parameters The parameters to be encoded and set in the request HTTP body.
 @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument: the OAuth credential returned by the server.
 @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error returned from the server.
 */
- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                               parameters:(NSDictionary *)parameters
                                                  success:(void (^)(GJSOAuthCredential *credential))success
                                                  failure:(void (^)(NSError *error))failure;

/*
 * 有些组织不按标准来处理，只好这里弄一下。
 */
- (NSURLSessionTask *)yuanxinAuthenticateUsingOAuthWithURLString:(NSString *)URLString
                                                      parameters:(NSDictionary *)parameters
                                                         success:(void (^)(id responseObjectl))success
                                                         failure:(void (^)(NSError *error))failure;

@end

NSString * const kGJSOAuthClientCredentialsGrantType = @"client_credentials";
NSString * const kGJSOAuthPasswordCredentialsGrantType = @"password";
NSString * const kGJSOAuthCodeGrantType = @"authorization_code";
NSString * const kGJSOAuthRefreshGrantType = @"refresh_token";

NSString * const GJSOAuth2ErrorDomain = @"com.alamofire.networking.oauth2.error";

// See: http://tools.ietf.org/html/rfc6749#section-5.2
static NSError * AFErrorFromRFC6749Section5_2Error(id object) {
    if (![object valueForKey:@"error"] || [[object valueForKey:@"error"] isEqual:[NSNull null]]) {
        return nil;
    }
    
    NSMutableDictionary *mutableUserInfo = [NSMutableDictionary dictionary];
    
    NSString *description = nil;
    if ([object valueForKey:@"error_description"]) {
        description = [object valueForKey:@"error_description"];
    } else {
        if ([[object valueForKey:@"error"] isEqualToString:@"invalid_request"]) {
            description = NSLocalizedStringFromTable(@"The request is missing a required parameter, includes an unsupported parameter value (other than grant type), repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the client, or is otherwise malformed.", @"AFOAuth2Manager", @"invalid_request");
        } else if ([[object valueForKey:@"error"] isEqualToString:@"invalid_client"]) {
            description = NSLocalizedStringFromTable(@"Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method).  The authorization server MAY return an HTTP 401 (Unauthorized) status code to indicate which HTTP authentication schemes are supported.  If the client attempted to authenticate via the \"Authorization\" request header field, the authorization server MUST respond with an HTTP 401 (Unauthorized) status code and include the \"WWW-Authenticate\" response header field matching the authentication scheme used by the client.", @"AFOAuth2Manager", @"invalid_request");
        } else if ([[object valueForKey:@"error"] isEqualToString:@"invalid_grant"]) {
            description = NSLocalizedStringFromTable(@"The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.", @"AFOAuth2Manager", @"invalid_request");
        } else if ([[object valueForKey:@"error"] isEqualToString:@"unauthorized_client"]) {
            description = NSLocalizedStringFromTable(@"The authenticated client is not authorized to use this authorization grant type.", @"AFOAuth2Manager", @"invalid_request");
        } else if ([[object valueForKey:@"error"] isEqualToString:@"unsupported_grant_type"]) {
            description = NSLocalizedStringFromTable(@"The authorization grant type is not supported by the authorization server.", @"AFOAuth2Manager", @"invalid_request");
        }
    }
    
    if (description) {
        mutableUserInfo[NSLocalizedDescriptionKey] = description;
    }
    
    if ([object valueForKey:@"error_uri"]) {
        mutableUserInfo[NSLocalizedRecoverySuggestionErrorKey] = [object valueForKey:@"error_uri"];
    }
    
    return [NSError errorWithDomain:GJSOAuth2ErrorDomain code:-1 userInfo:mutableUserInfo];
}

@interface GJSOAuthManager()

@property (readwrite, nonatomic, copy) NSString *serviceProviderIdentifier;
@property (readwrite, nonatomic, copy) NSString *clientID;
@property (readwrite, nonatomic, copy) NSString *secret;

@end

@implementation GJSOAuthManager

+ (instancetype) managerWithBaseURL:(NSURL *)url
                           clientID:(NSString *)clientID
                             secret:(NSString *)secret {
    return [self managerWithBaseURL:url sessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] clientID:clientID secret:secret];
}

+ (instancetype) managerWithBaseURL:(NSURL *)url
               sessionConfiguration:(NSURLSessionConfiguration *)configuration
                           clientID:(NSString *)clientID
                             secret:(NSString *)secret {
    return [[self alloc] initWithBaseURL:url sessionConfiguration:configuration clientID:clientID secret:secret];
}

- (id)initWithBaseURL:(NSURL *)url
             clientID:(NSString *)clientID
               secret:(NSString *)secret {
    return [self initWithBaseURL:url sessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] clientID:clientID secret:secret];
}

- (id)initWithBaseURL:(NSURL *)url
 sessionConfiguration:(NSURLSessionConfiguration *)configuration
             clientID:(NSString *)clientID
               secret:(NSString *)secret {
    NSParameterAssert(url);
    NSParameterAssert(clientID);
    NSParameterAssert(secret);
    
    self = [super initWithBaseURL:url sessionConfiguration:configuration];
    if (!self) {
        return nil;
    }
    
    self.serviceProviderIdentifier = [self.baseURL host];
    self.clientID = clientID;
    self.secret = secret;
    self.useHTTPBasicAuthentication = YES;
    
    [self.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    
    return self;
}

#pragma mark -

- (void)setUseHTTPBasicAuthentication:(BOOL)useHTTPBasicAuthentication {
    _useHTTPBasicAuthentication = useHTTPBasicAuthentication;
    
    if (self.useHTTPBasicAuthentication) {
        [self.requestSerializer setAuthorizationHeaderFieldWithUsername:self.clientID password:self.secret];
    } else {
        [self.requestSerializer setValue:nil forHTTPHeaderField:@"Authorization"];
    }
}

- (void)setSecret:(NSString *)secret {
    if (!secret) {
        secret = @"";
    }
    
    _secret = secret;
}

#pragma mark -

- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                                 username:(NSString *)username
                                                 password:(NSString *)password
                                                    scope:(NSString *)scope
                                                  success:(void (^)(GJSOAuthCredential * _Nonnull))success
                                                  failure:(void (^)(NSError * _Nonnull))failure {
    NSParameterAssert(username);
    NSParameterAssert(password);
    
    NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
    [parameters setValue:kGJSOAuthPasswordCredentialsGrantType forKey:@"grant_type"];
    [parameters setValue:username forKey:@"username"];
    [parameters setValue:password forKey:@"password"];
    
    if (scope) {
        [parameters setValue:scope forKey:@"scope"];
    }
    
    return [self authenticateUsingOAuthWithURLString:URLString parameters:parameters success:success failure:failure];
}

- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                                    scope:(NSString *)scope
                                                  success:(void (^)(GJSOAuthCredential * _Nonnull))success
                                                  failure:(void (^)(NSError * _Nonnull))failure {
    
    NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
    [parameters setValue:kGJSOAuthClientCredentialsGrantType forKey:@"grant_type"];
    
    if (scope) {
        [parameters setValue:scope forKey:@"scope"];
    }
    
    return [self authenticateUsingOAuthWithURLString:URLString parameters:parameters success:success failure:failure];
}


- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                             refreshToken:(NSString *)refreshToken
                                                  success:(void (^)(GJSOAuthCredential *credential))success
                                                  failure:(void (^)(NSError *error))failure
{
    NSParameterAssert(refreshToken);
    
    NSDictionary *parameters = @{
                                 @"grant_type": kGJSOAuthRefreshGrantType,
                                 @"refresh_token": refreshToken
                                 };
    
    return [self authenticateUsingOAuthWithURLString:URLString parameters:parameters success:success failure:failure];
}

- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                                     code:(NSString *)code
                                              redirectURI:(NSString *)uri
                                                  success:(void (^)(GJSOAuthCredential *credential))success
                                                  failure:(void (^)(NSError *error))failure
{
    NSParameterAssert(code);
    NSParameterAssert(uri);
    
    NSDictionary *parameters = @{
                                 @"grant_type": kGJSOAuthCodeGrantType,
                                 @"code": code,
                                 @"redirect_uri": uri
                                 };
    
    return [self authenticateUsingOAuthWithURLString:URLString parameters:parameters success:success failure:failure];
}

- (NSURLSessionTask *)yuanxinAuthenticateUsingOAuthWithURLString:(NSString *)URLString
                                                      parameters:(NSDictionary *)parameters
                                                         success:(void (^)(id responseObjectl))success
                                                         failure:(void (^)(NSError *error))failure
{
    NSMutableDictionary *mutableParameters = [NSMutableDictionary dictionaryWithDictionary:parameters];
    if (!self.useHTTPBasicAuthentication) {
        mutableParameters[@"client_id"] = self.clientID;
        mutableParameters[@"client_secret"] = self.secret;
    }
    parameters = [NSDictionary dictionaryWithDictionary:mutableParameters];
    
    NSURLSessionTask *task;
    task = [self POST:URLString parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (!responseObject) {
            if (failure) {
                failure(nil);
            }
            return;
        }
        
        if ([responseObject valueForKey:@"error"]) {
            if (failure) {
                failure(AFErrorFromRFC6749Section5_2Error(responseObject));
            }
            return;
        }
        
        if (success) {
            success(responseObject);
        }
        /*
         移出到外面
         */
        
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        if (failure) {
            failure(error);
        }
    }];
    
    return task;
}

- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                               parameters:(NSDictionary *)parameters
                                                  success:(void (^)(GJSOAuthCredential *credential))success
                                                  failure:(void (^)(NSError *error))failure
{
    NSMutableDictionary *mutableParameters = [NSMutableDictionary dictionaryWithDictionary:parameters];
    if (!self.useHTTPBasicAuthentication) {
        mutableParameters[@"client_id"] = self.clientID;
        mutableParameters[@"client_secret"] = self.secret;
    }
    parameters = [NSDictionary dictionaryWithDictionary:mutableParameters];
    
    NSURLSessionTask *task;
    task = [self POST:URLString parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (!responseObject) {
            if (failure) {
                failure(nil);
            }
            return;
        }
        
        if ([responseObject valueForKey:@"error"]) {
            if (failure) {
                failure(AFErrorFromRFC6749Section5_2Error(responseObject));
            }
            return;
        }
        
        NSString *refreshToken = [responseObject valueForKey:@"refresh_token"];
        if (!refreshToken || [refreshToken isEqual:[NSNull null]]) {
            refreshToken = [parameters valueForKey:@"refresh_token"];
        }
        
        GJSOAuthCredential *credential = [GJSOAuthCredential credentialWithOAuthToken:[responseObject valueForKey:@"access_token"] tokenType:[responseObject valueForKey:@"token_type"]];
        
        
        if (refreshToken) { // refreshToken is optional in the OAuth2 spec
            [credential setRefreshToken:refreshToken];
        }
        
        // Expiration is optional, but recommended in the OAuth2 spec. It not provide, assume distantFuture === never expires
        NSDate *expireDate = [NSDate distantFuture];
        id expiresIn = [responseObject valueForKey:@"expires_in"];
        if (expiresIn && ![expiresIn isEqual:[NSNull null]]) {
            expireDate = [NSDate dateWithTimeIntervalSinceNow:[expiresIn doubleValue]];
        }
        
        if (expireDate) {
            [credential setExpiration:expireDate];
        }
        
        if (success) {
            success(credential);
        }
        
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        if (failure) {
            failure(error);
        }
    }];
    
    return task;
}

@end

@implementation NSString (URL)

- (NSString *)trim {
    
    if(self){
        NSString *cleanString = [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        return cleanString;
    }else{
        return nil;
    }
}

- (BOOL)isUrl {
    BOOL result = false;
    if(self){
        NSString *cleanString = [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        NSRange bankRang = NSMakeRange(0, 7);
        //todo:ydz 因为生产环境还不是https
        result = [[cleanString substringWithRange:bankRang] caseInsensitiveCompare:@"http://"];
        if(!result){
            bankRang.length = 8;
            result = [[cleanString substringWithRange:bankRang] caseInsensitiveCompare:@"https://"];
        }
    }
    
    return result;
}

@end

@implementation GJSHTTPRequestSerializer (OAuth)

- (void)setAuthorizationHeaderFieldWithCredential:(GJSOAuthCredential *)credential {
    if ([credential.tokenType compare:@"Bearer" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
        [self setValue:[NSString stringWithFormat:@"Bearer %@", credential.accessToken] forHTTPHeaderField:@"Authorization"];
    }
}

@end

@interface GJSUtilHTTPRequestClient() <NSURLSessionDelegate,NSURLSessionTaskDelegate>

@property (nonatomic, strong) GJSOAuthManager *defatltManager;
@property (readwrite, nonatomic, strong) GJSOAuthCredential *credential;
//还原Tokenkey
@property (nonatomic, strong) NSString *serviceProviderIdentifier;
@property (nonatomic, strong) NSMutableArray<id<GJSOAuthInfoObserver>> *oauthObservers;

@property (nonatomic, strong) NSURLSessionConfiguration *configuration;
@property (nonatomic, strong) NSURLSession *sessionClient;

@property (nonatomic, assign) BOOL triedAgain; // for 401
@property (nonatomic, strong) NSMutableArray<TryAgainBlock> *arryTryAgain; // for 401

@end

@implementation GJSUtilHTTPRequestClient

+ (instancetype)shareInstance{
    static GJSUtilHTTPRequestClient *_networkingManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _networkingManager = [[GJSUtilHTTPRequestClient alloc] init];
    });
    
    return _networkingManager;
}

- (instancetype)init{
    self = [super init];
    if(self){
        _configuration= [NSURLSessionConfiguration defaultSessionConfiguration];
        _configuration.timeoutIntervalForRequest = 120.0;
        _configuration.timeoutIntervalForResource = 120.0;
        _timeoutInterval = 30;
        _oauthObservers = [[NSMutableArray<id<GJSOAuthInfoObserver>> alloc] init];
        
        self.arryTryAgain = [[NSMutableArray alloc] init];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_refreshToken:) name:kNetworkRefreshToken object:nil];
    }
    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (GJSOAuthManager *)defatltManager{
    if(!_defatltManager){
        _defatltManager = [[GJSOAuthManager alloc] initWithBaseURL:self.baseURL
                                            sessionConfiguration:_configuration
                                                        clientID:self.clientID
                                                          secret:self.clientSecret];
        
        _defatltManager.requestSerializer = [GJSJSONRequestSerializer serializer];
        _defatltManager.requestSerializer.timeoutInterval = self.timeoutInterval;
        
        __weak typeof (self) weakSelf = self;
        // 会话收到挑战时
        [_defatltManager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing  _Nullable * _Nullable credential) {
            NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            //如果是http代理，其它代理方法类似
            if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodNTLM])
            {
                if ([challenge previousFailureCount] == 0) {
                    /* SET YOUR credentials, i'm just hard coding them in, tweak as necessary */
                    __autoreleasing NSURLCredential * _Nullable weakCredential = [NSURLCredential credentialWithUser:weakSelf.userName password:weakSelf.password persistence:NSURLCredentialPersistencePermanent];
                    disposition = NSURLSessionAuthChallengeUseCredential;
                    credential = &weakCredential;
                } else {
                    disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                }
            } else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
                if (challenge.previousFailureCount == 0) {
                    __autoreleasing NSURLCredential * _Nullable weakCredential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                    disposition = NSURLSessionAuthChallengeUseCredential;
                    credential = &weakCredential;
                }else {
                    disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                }
            }
            
            return disposition;
        }];
    }
    
    return _defatltManager;
}

//添加对OAuth 服务状态管理
- (void)addOAuthInfoObserver:(id<GJSOAuthInfoObserver>)oauthObserver{
    [_oauthObservers addObject:oauthObserver];
}

//移除当前OAuth 服务状态管理
- (void)removeOAuthInfoObserver:(id<GJSOAuthInfoObserver>)oauthObserver{
    [_oauthObservers removeObject:oauthObserver];
}

- (BOOL)retrieveCredentialWithIdentifier:(NSString *)identifier{
    if(identifier){
        
        self.serviceProviderIdentifier = identifier;
        
        GJSOAuthCredential *credential =[GJSOAuthCredential retrieveCredentialWithIdentifier:identifier];
        if(credential){
            _credential = credential;
            
            return YES;
        }
        
    }
    return NO;
}

#pragma mark lazy load
- (NSURLSession *)sessionClient {
    if (!_sessionClient) {
        // 初始化一个请求类专门负责预请求
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
        _sessionClient = session;
    }
    
    return _sessionClient;
}

// 参考 https://www.jianshu.com/p/a8fc22afb739
// demo
- (void)sendRequest{
    //创建请求
    NSURL *url = [NSURL URLWithString:@"http://httpbin.org/get"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    //设置request的缓存策略（决定该request是否要从缓存中获取）
    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
    
    //创建配置（决定要不要将数据和响应缓存在磁盘）
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //configuration.requestCachePolicy = NSURLRequestReturnCacheDataElseLoad;
    
    //创建会话
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    //生成任务
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    //创建的task是停止状态，需要我们去启动
    [task resume];
}

#pragma mark -- methods

- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                                 username:(NSString *)username
                                                 password:(NSString *)password
                                                    scope:(NSString *)scope
                                                  success:(void (^)(GJSOAuthCredential *credential))success
                                                  failure:(void (^)(NSError *error))failure{
    
    GJSOAuthManager *oAuthManager = self.defatltManager;
    __weak typeof (self) weakSelf = self;
    return [oAuthManager authenticateUsingOAuthWithURLString:URLString
                                                    username:username
                                                    password:password
                                                       scope:scope
                                                     success:^(GJSOAuthCredential *credential) {
                                                         NSLog(@"Token: %@", credential.accessToken);
                                                         weakSelf.credential =credential;
                                                         /*
                                                          [GJSOAuthCredential storeCredential:credential
                                                          withIdentifier:serviceProviderIdentifier];
                                                          */
                                                         if(success){
                                                             success(credential);
                                                         }
                                                     }
                                                     failure:^(NSError *error) {
                                                         NSLog(@"Error: %@", error);
                                                         if(failure){
                                                             failure(error);
                                                         }
                                                     }];
    
}

- (NSURLSessionTask *)authenticateUsingOAuthWithURLString:(NSString *)URLString
                                               parameters:(NSDictionary *)parameters
                                                  success:(void (^)(id responseObject))success
                                                  failure:(void (^)(NSError *error))failure{
    
    GJSOAuthManager *oAuthManager = self.defatltManager;
    __weak typeof (self) weakSelf = self;
    return [oAuthManager yuanxinAuthenticateUsingOAuthWithURLString:URLString
                                                         parameters:parameters
                                                            success:^(id responseObject) {
                                                                
                                                                NSString *refreshToken = [responseObject valueForKey:@"refresh_token"];
                                                                if (!refreshToken || [refreshToken isEqual:[NSNull null]]) {
                                                                    refreshToken = [parameters valueForKey:@"refresh_token"];
                                                                }
                                                                
                                                                GJSOAuthCredential *credential = [GJSOAuthCredential credentialWithOAuthToken:[responseObject valueForKey:@"access_token"] tokenType:[responseObject valueForKey:@"token_type"]];
                                                                
                                                                
                                                                if (refreshToken) { // refreshToken is optional in the OAuth2 spec
                                                                    [credential setRefreshToken:refreshToken];
                                                                }
                                                                
                                                                // Expiration is optional, but recommended in the OAuth2 spec. It not provide, assume distantFuture === never expires
                                                                NSDate *expireDate = [NSDate distantFuture];
                                                                id expiresIn = [responseObject valueForKey:@"expires_in"];
                                                                if (expiresIn && ![expiresIn isEqual:[NSNull null]]) {
                                                                    expireDate = [NSDate dateWithTimeIntervalSinceNow:[expiresIn doubleValue]];
                                                                }
                                                                
                                                                if (expireDate) {
                                                                    [credential setExpiration:expireDate];
                                                                }
                                                                
                                                                weakSelf.credential = credential;
                                                                NSLog(@"Token: %@", credential.accessToken);
                                                                
                                                                [GJSOAuthCredential storeCredential:credential
                                                                                    withIdentifier:self.serviceProviderIdentifier];
                                                                
                                                                
                                                                if(success){
                                                                    success(responseObject);
                                                                }
                                                            }
                                                            failure:^(NSError *error) {
                                                                NSLog(@"Error: %@", error);
                                                                if(failure){
                                                                    failure(error);
                                                                }
                                                            }];
    
}

- (void)handle401:(NSURLSessionDataTask *)task error:(NSError *)error block:(TryAgainBlock)tryAgainBlock failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
    // 缓存执行
    if (self.arryTryAgain) {
        [self.arryTryAgain addObject:tryAgainBlock];
        // 设置超时
        dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
        dispatch_after(delayTime, dispatch_get_main_queue(), ^{
            if ([self.arryTryAgain containsObject:tryAgainBlock]) {
                [self.arryTryAgain removeObject:tryAgainBlock];
                if(failure){
                    failure(task,error);
                }
            }
        });
    }
}

- (void)_refreshToken:(NSNotification *)notification {
    NSDictionary *obj = (NSDictionary *)notification.object;
    
    @synchronized (self) {
        for (TryAgainBlock tryAgainBlock in self.arryTryAgain) {
            tryAgainBlock();
        }
        [self.arryTryAgain removeAllObjects];
    }
    
    self.triedAgain = YES;
}

- (GJSOAuthManager *)getCurrentManagerWithUrl:(NSString *)URLString{
    GJSOAuthManager *manager = nil;
    if([URLString hasPrefix:@"https://"] || [URLString hasPrefix:@"http://"]){
        manager = [[GJSOAuthManager alloc] initWithSessionConfiguration:nil];
    }else{
        manager = self.defatltManager;
    }
    return manager;
}

- (NSURL *)getCurrentUrl:(NSString *)URLString{
    
    NSURL *url = nil;
    if([URLString isUrl]){
        url = [NSURL URLWithString:URLString];
    }else{
        url = [NSURL URLWithString:URLString relativeToURL:self.baseURL];
    }
    return url;
}

#pragma mark -

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                      success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                      failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    
    return [self GET:URLString parameters:parameters progress:nil success:success failure:failure];
}

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    
    GJSOAuthManager *manager = [self getCurrentManagerWithUrl:URLString];
    if(self.credential && self.credential.accessToken){
        [manager.requestSerializer setAuthorizationHeaderFieldWithCredential:self.credential];
    }
    __weak typeof (self) weakSelf = self;
    
    return [manager GET:[self getCurrentUrl:URLString].absoluteString
             parameters:parameters
               progress:downloadProgress
                success:^(NSURLSessionDataTask *task, id _Nullable responseObject){
                    self.triedAgain = NO; // 成功时重置
                    if(success){
                        success(task,responseObject);
                    }
                }
                failure:^(NSURLSessionDataTask * _Nullable task, NSError *error){
                    if(error.code == 401){
                        __strong typeof (weakSelf) strongSelf = weakSelf;
                        if(strongSelf.oauthObservers.count >0 ){
                            for (id<GJSOAuthInfoObserver> oaObserver in strongSelf.oauthObservers){
                                if([oaObserver respondsToSelector:@selector(onUnauthorized)]){
                                    [oaObserver onUnauthorized];
                                }
                            }
                        }
                        
                        if (self.triedAgain) {
                            self.triedAgain = NO;
                        } else {
                            TryAgainBlock tryAgainBlock = ^(){
                                // 重试一次
                                [strongSelf GET:[self getCurrentUrl:URLString].absoluteString parameters:parameters success:success failure:failure];
                            };
                            [strongSelf handle401:task error:error block:tryAgainBlock failure:failure];
                            
                            return; // 停掉，重试后再回调
                        }
                    }
                    
                    if(failure){
                        failure(task,error);
                    }
                }];
    
}

- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
                       success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                       failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    return [self POST:URLString parameters:parameters progress:nil success:success failure:failure];
}

- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
                      progress:(void (^)(NSProgress * _Nonnull))uploadProgress
                       success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                       failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    
    GJSOAuthManager *manager = [self getCurrentManagerWithUrl:URLString];
    if(self.credential && self.credential.accessToken){
        [manager.requestSerializer setAuthorizationHeaderFieldWithCredential:self.credential];
    }
    __weak typeof (self) weakSelf = self;
    return [manager POST:[self getCurrentUrl:URLString].absoluteString
              parameters:parameters
                progress:uploadProgress
                 success:^(NSURLSessionDataTask *task, id _Nullable responseObject){
                     self.triedAgain = NO; // 成功时重置
                     if(success){
                         success(task,responseObject);
                     }
                 }
                 failure:^(NSURLSessionDataTask * _Nullable task, NSError *error){
                     if(error.code == 401){
                         __strong typeof (weakSelf) strongSelf = weakSelf;
                         if(strongSelf.oauthObservers.count >0 ){
                             for (id<GJSOAuthInfoObserver> oaObserver in strongSelf.oauthObservers){
                                 if([oaObserver respondsToSelector:@selector(onUnauthorized)]){
                                     [oaObserver onUnauthorized];
                                 }
                             }
                         }
                         
                         if (self.triedAgain) {
                             self.triedAgain = NO;
                         } else {
                             TryAgainBlock tryAgainBlock = ^(){
                                 // 重试一次
                                 [strongSelf POST:[self getCurrentUrl:URLString].absoluteString parameters:parameters success:success failure:failure];
                             };
                             [strongSelf handle401:task error:error block:tryAgainBlock failure:failure];
                             
                             return; // 停掉，重试后再回调
                         }
                     }
                     
                     if(failure){
                         failure(task,error);
                     }
                 }];
}

- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(nullable id)parameters
     constructingBodyWithBlock:(nullable void (^)(id<GJSMultipartFormData> _Nonnull))block
                       success:(nullable void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                       failure:(nullable void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    return [self POST:URLString parameters:parameters constructingBodyWithBlock:block progress:nil success:success failure:failure];
}

- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
     constructingBodyWithBlock:(void (^)(id <GJSMultipartFormData> formData))block
                      progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
                       success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                       failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    GJSOAuthManager *manager = [self getCurrentManagerWithUrl:URLString];
    if(self.credential && self.credential.accessToken){
        [manager.requestSerializer setAuthorizationHeaderFieldWithCredential:self.credential];
    }
    return [manager POST:[self getCurrentUrl:URLString].absoluteString
              parameters:parameters constructingBodyWithBlock:block
                progress:uploadProgress
                 success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                     self.triedAgain = NO; // 成功时重置
                     if(success){
                         success(task,responseObject);
                     }
                 }
                 failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                     if(failure){
                         failure(task,error);
                     }
                 }];
}

- (NSURLSessionDataTask *)uploadMultiPart:(NSString *)URLString
                               parameters:(id)parameters
                                     data:(NSData *)data
                                  success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                                  failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    return [self uploadMultiPart:URLString parameters:parameters data:data progress:nil success:success failure:failure];
}

- (NSURLSessionDataTask *)uploadMultiPart:(NSString *)URLString
                               parameters:(id)parameters
                                     data:(NSData *)data
                                 progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
                                  success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                                  failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    NSURL *URL = [self getCurrentUrl:URLString];
    NSMutableURLRequest *request = [[GJSHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:[URL absoluteString] parameters:parameters constructingBodyWithBlock:^(id<GJSMultipartFormData> formData) {
        //[formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
        [formData appendPartWithFormData:data name:@"files"];
    } error:nil];
    
    GJSURLSessionManager *manager = [[GJSURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    
    __block  NSURLSessionUploadTask *uploadTask;
    uploadTask = [manager uploadTaskWithStreamedRequest:request
                                               progress:uploadProgress
                                      completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                                          if (error) {
                                              if(failure){
                                                  failure(uploadTask,error);
                                              }
                                              
                                              NSLog(@"Error: %@", error);
                                          } else {
                                              NSLog(@"%@ %@", response, responseObject);
                                              if(success){
                                                  
                                              }
                                          }
                                      }];
    
    [uploadTask resume];
    
    return uploadTask;
}

- (NSURLSessionDataTask *)uploadFile:(NSString *)URLString
                                data:(NSData *)data
                             headers:(NSDictionary *)headers
                             success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                             failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    return [self uploadFile:URLString data:data headers:headers progress:nil success:success failure:failure];
}

- (NSURLSessionDataTask *)uploadFile:(NSString *)URLString
                                data:(NSData *)data
                             headers:(NSDictionary *)headers
                            progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
                             success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                             failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    
    GJSOAuthManager *manager = [self getCurrentManagerWithUrl:URLString];
    if(self.credential && self.credential.accessToken){
        [manager.requestSerializer setAuthorizationHeaderFieldWithCredential:self.credential];
    }
    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[self getCurrentUrl:URLString]];
    [request setHTTPMethod:@"POST"];
    
    for (NSString *key in headers) {
        id val = [headers objectForKey:key];
        if ([val respondsToSelector:@selector(stringValue)]) {
            val = [val stringValue];
        }
        if (![val isKindOfClass:[NSString class]]) {
            continue;
        }
        [request setValue:val forHTTPHeaderField:key];
    }
    
    __block NSURLSessionUploadTask *uploadTask = nil;
    uploadTask = [manager uploadTaskWithRequest:request
                                       fromData:data
                                       progress:nil
                              completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                                  if (error) {
                                      NSString *message = [responseObject  objectForKey:@"msg"];
                                      message = error.localizedDescription;
                                      if(failure){
                                          failure(uploadTask,error);
                                      }
                                      
                                      NSLog(@"Error: %@", error);
                                  } else {
                                      NSLog(@"%@ %@", response, responseObject);
                                      if(success){
                                          success(uploadTask,responseObject);
                                      }
                                  }
                              }];
    [uploadTask resume];
    
    return uploadTask;
}

- (NSURLSessionDownloadTask *)downloadFile:(NSString *)URLString
                          headerAccept:(NSString *)headerAccept
                              cacheURL:(NSURL *)cacheUrl
                      downloadProgress:(void (^)(NSProgress *downloadProgress))downloadProgressBlock
                               success:(void (^)(NSURLSessionDownloadTask *task, id responseObject))success
                               failure:(void (^)(NSURLSessionDownloadTask *task, NSError *error))failure{
    
    GJSOAuthManager *manager = [self getCurrentManagerWithUrl:URLString];
    if(self.credential && self.credential.accessToken){
        [manager.requestSerializer setAuthorizationHeaderFieldWithCredential:self.credential];
    }
    manager.responseSerializer = [GJSHTTPResponseSerializer serializer];
    //[AFImageResponseSerializer serializer];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[self getCurrentUrl:URLString]];
    //NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    [request setHTTPMethod:@"GET"];
    if (headerAccept) {
        [request addValue:headerAccept forHTTPHeaderField:@"Accept"];
    }
    __block NSURL *storageUrl = [cacheUrl copy];
    __block NSURLSessionDownloadTask *downloadTask =nil;
    downloadTask = [manager downloadTaskWithRequest:request
                                           progress:^(NSProgress *downloadProgress){
                                               if(downloadProgressBlock){
                                                   downloadProgressBlock(downloadProgress);
                                               }
                                           }
                                        destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
                                            
                                            /*
                                             NSString *filename =  [response suggestedFilename];
                                             NSString * path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
                                             
                                             NSURL *localPath = [NSURL URLWithString:filename relativeToURL:path];
                                             return localPath;
                                             */
                                            return storageUrl;
                                            
                                        } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
                                            if(!error){
                                                if(success){
                                                    success(downloadTask,filePath);
                                                }
                                            }else{
                                                if(failure){
                                                    failure(downloadTask,error);
                                                }
                                            }
                                            NSLog(@"File downloaded to: %@", filePath);
                                        }];
    [downloadTask resume];
    
    return downloadTask;
    
}

@end
